from playwright.sync_api import Page, Locator, ElementHandle
from typing import Optional, Union, List
import time

class BaseElement:
    """基础元素类，封装通用元素操作"""
    
    def __init__(self, page: Page, locator: Union[str, Locator], description: str = ""):
        """
        初始化元素
        
        :param page: Playwright的Page对象
        :param locator: 元素定位符，可为字符串或Locator对象
        :param description: 元素描述，用于日志和错误信息
        """
        self.page = page
        self._locator = locator if isinstance(locator, Locator) else page.locator(locator)
        self.description = description or f"元素: {locator}"
    
    @property
    def locator(self) -> Locator:
        """获取元素的Locator对象"""
        return self._locator
    
    def wait_for(self, state: str = "visible", timeout: int = 30000) -> None:
        """
        等待元素达到指定状态
        
        :param state: 等待的状态，可选值: "visible", "hidden", "attached", "detached"
        :param timeout: 超时时间(毫秒)
        """
        try:
            self.locator.wait_for(state=state, timeout=timeout)
        except Exception as e:
            raise Exception(f"等待{self.description}达到{state}状态失败: {str(e)}")
    
    def is_visible(self, timeout: int = 5000) -> bool:
        """
        检查元素是否可见
        
        :param timeout: 超时时间(毫秒)
        :return: 元素是否可见
        """
        try:
            self.locator.wait_for(state="visible", timeout=timeout)
            return True
        except:
            return False
    
    def is_hidden(self, timeout: int = 5000) -> bool:
        """
        检查元素是否隐藏
        
        :param timeout: 超时时间(毫秒)
        :return: 元素是否隐藏
        """
        try:
            self.locator.wait_for(state="hidden", timeout=timeout)
            return True
        except:
            return False
    
    def click(self, force: bool = False, timeout: int = 30000) -> None:
        """
        点击元素
        
        :param force: 是否强制点击，忽略可点击状态检查
        :param timeout: 超时时间(毫秒)
        """
        try:
            self.locator.click(force=force, timeout=timeout)
        except Exception as e:
            raise Exception(f"点击{self.description}失败: {str(e)}")
    
    def double_click(self, timeout: int = 30000) -> None:
        """
        双击元素
        
        :param timeout: 超时时间(毫秒)
        """
        try:
            self.locator.dblclick(timeout=timeout)
        except Exception as e:
            raise Exception(f"双击{self.description}失败: {str(e)}")
    
    def get_text(self, timeout: int = 30000) -> str:
        """
        获取元素文本
        
        :param timeout: 超时时间(毫秒)
        :return: 元素文本内容
        """
        try:
            self.wait_for(timeout=timeout)
            return self.locator.text_content(timeout=timeout).strip()
        except Exception as e:
            raise Exception(f"获取{self.description}文本失败: {str(e)}")
    
    def hover(self, timeout: int = 30000) -> None:
        """
        鼠标悬停在元素上
        
        :param timeout: 超时时间(毫秒)
        """
        try:
            self.locator.hover(timeout=timeout)
        except Exception as e:
            raise Exception(f"鼠标悬停{self.description}失败: {str(e)}")
    
    def count(self) -> int:
        """
        获取匹配元素的数量
        
        :return: 元素数量
        """
        return self.locator.count()
    
    def nth(self, index: int) -> 'BaseElement':
        """
        获取匹配元素中的第n个元素
        
        :param index: 索引
        :return: 新的BaseElement实例
        """
        return BaseElement(
            self.page, 
            self.locator.nth(index), 
            f"{self.description}的第{index}个元素"
        )


class Button(BaseElement):
    """按钮元素类，继承基础元素并扩展按钮特定操作"""
    
    def __init__(self, page: Page, locator: Union[str, Locator], description: str = ""):
        super().__init__(page, locator, description or "按钮")
    
    def click_and_wait(self, url: Optional[str] = None, timeout: int = 60000) -> None:
        """
        点击按钮并等待页面加载
        
        :param url: 等待的URL，None则等待任意导航
        :param timeout: 超时时间(毫秒)
        """
        try:
            with self.page.expect_navigation(url=url, timeout=timeout):
                self.click()
        except Exception as e:
            raise Exception(f"点击{self.description}并等待导航失败: {str(e)}")


class Input(BaseElement):
    """输入框元素类，继承基础元素并扩展输入框特定操作"""
    
    def __init__(self, page: Page, locator: Union[str, Locator], description: str = ""):
        super().__init__(page, locator, description or "输入框")
    
    def fill(self, text: str, timeout: int = 30000) -> None:
        """
        向输入框填充文本
        
        :param text: 要填充的文本
        :param timeout: 超时时间(毫秒)
        """
        try:
            self.locator.fill(text, timeout=timeout)
        except Exception as e:
            raise Exception(f"向{self.description}填充文本失败: {str(e)}")
    
    def clear(self, timeout: int = 30000) -> None:
        """
        清空输入框
        
        :param timeout: 超时时间(毫秒)
        """
        try:
            self.locator.clear(timeout=timeout)
        except Exception as e:
            raise Exception(f"清空{self.description}失败: {str(e)}")
    
    def type(self, text: str, delay: int = 100, timeout: int = 30000) -> None:
        """
        模拟键盘输入文本
        
        :param text: 要输入的文本
        :param delay: 按键间隔时间(毫秒)
        :param timeout: 超时时间(毫秒)
        """
        try:
            self.locator.type(text, delay=delay, timeout=timeout)
        except Exception as e:
            raise Exception(f"向{self.description}输入文本失败: {str(e)}")
    
    def get_value(self, timeout: int = 30000) -> str:
        """
        获取输入框的值
        
        :param timeout: 超时时间(毫秒)
        :return: 输入框的值
        """
        try:
            self.wait_for(timeout=timeout)
            value = self.locator.input_value(timeout=timeout)
            return value.strip() if value else ""
        except Exception as e:
            raise Exception(f"获取{self.description}值失败: {str(e)}")


class Checkbox(BaseElement):
    """复选框元素类"""
    
    def __init__(self, page: Page, locator: Union[str, Locator], description: str = ""):
        super().__init__(page, locator, description or "复选框")
    
    def check(self, timeout: int = 30000) -> None:
        """勾选复选框"""
        try:
            self.locator.check(timeout=timeout)
        except Exception as e:
            raise Exception(f"勾选{self.description}失败: {str(e)}")
    
    def uncheck(self, timeout: int = 30000) -> None:
        """取消勾选复选框"""
        try:
            self.locator.uncheck(timeout=timeout)
        except Exception as e:
            raise Exception(f"取消勾选{self.description}失败: {str(e)}")
    
    def is_checked(self, timeout: int = 30000) -> bool:
        """检查复选框是否被勾选"""
        try:
            self.wait_for(timeout=timeout)
            return self.locator.is_checked(timeout=timeout)
        except Exception as e:
            raise Exception(f"检查{self.description}勾选状态失败: {str(e)}")


class Select(BaseElement):
    """下拉选择框元素类"""
    
    def __init__(self, page: Page, locator: Union[str, Locator], description: str = ""):
        super().__init__(page, locator, description or "下拉选择框")
    
    def select_option(self, option: Union[str, int, dict], timeout: int = 30000) -> None:
        """
        选择下拉选项
        
        :param option: 选项，可以是值、索引或包含label/value的字典
        :param timeout: 超时时间(毫秒)
        """
        try:
            self.locator.select_option(option, timeout=timeout)
        except Exception as e:
            raise Exception(f"{self.description}选择选项失败: {str(e)}")
    
    def get_selected_options(self, timeout: int = 30000) -> List[str]:
        """
        获取当前选中的选项
        
        :param timeout: 超时时间(毫秒)
        :return: 选中选项的列表
        """
        try:
            self.wait_for(timeout=timeout)
            options = self.locator.input_value(timeout=timeout)
            return options.split(',') if options else []
        except Exception as e:
            raise Exception(f"获取{self.description}选中选项失败: {str(e)}")


class BasePage:
    """页面基础类，所有页面对象类的父类"""
    
    def __init__(self, page: Page, url: Optional[str] = None):
        """
        初始化页面
        
        :param page: Playwright的Page对象
        :param url: 页面URL
        """
        self.page = page
        self.url = url
        # 初始化页面元素
        self._init_elements()
    
    def _init_elements(self) -> None:
        """初始化页面元素，子类应重写此方法"""
        pass
    
    def navigate(self, timeout: int = 60000) -> None:
        """
        导航到页面URL
        
        :param timeout: 超时时间(毫秒)
        """
        if not self.url:
            raise Exception("页面URL未设置")
        
        try:
            self.page.goto(self.url, timeout=timeout)
        except Exception as e:
            raise Exception(f"导航到页面{self.url}失败: {str(e)}")
    
    def wait_for_load(self, timeout: int = 60000) -> None:
        """
        等待页面加载完成
        
        :param timeout: 超时时间(毫秒)
        """
        try:
            self.page.wait_for_load_state("load", timeout=timeout)
        except Exception as e:
            raise Exception(f"等待页面加载完成失败: {str(e)}")
    
    def title(self) -> str:
        """获取页面标题"""
        return self.page.title()
    
    def screenshot(self, path: str) -> None:
        """
        截取页面截图
        
        :param path: 截图保存路径
        """
        self.page.screenshot(path=path)
